package com.github.kpavlov.jreactive8583.client;
import com.github.kpavlov.jreactive8583.AbstractIso8583Connector;
import com.github.kpavlov.jreactive8583.netty.pipeline.Iso8583ChannelInitializer;
import com.github.kpavlov.jreactive8583.netty.pipeline.ReconnectOnCloseListener;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.MessageFactory;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class Iso8583Client<T extends IsoMessage> extends AbstractIso8583Connector<ClientConfiguration, Bootstrap, T> {
private ReconnectOnCloseListener reconnectOnCloseListener;
public Iso8583Client(SocketAddress socketAddress, ClientConfiguration config, MessageFactory<T> isoMessageFactory) {
super(config, isoMessageFactory);
setSocketAddress(socketAddress);
}
public Iso8583Client(SocketAddress socketAddress, MessageFactory<T> isoMessageFactory) {
this(socketAddress, ClientConfiguration.getDefault(), isoMessageFactory);
}
/**
* @deprecated Use {@link #Iso8583Client(SocketAddress, ClientConfiguration, MessageFactory)}
*
* @param isoMessageFactory message factory
*/
@Deprecated
public Iso8583Client(MessageFactory<T> isoMessageFactory) {
super(ClientConfiguration.getDefault(), isoMessageFactory);
}
/**
* Connects synchronously to remote address.
*
* @return Returns the {@link ChannelFuture} which will be notified when this
* channel is closed.
* @throws InterruptedException if connection process was interrupted
* @see #setSocketAddress(SocketAddress)
*/
public ChannelFuture connect() throws InterruptedException {
final Channel channel = connectAsync().sync().channel();
assert (channel != null) : "Channel must be set";
setChannel(channel);
return channel.closeFuture();
}
/**
* Connect synchronously to specified host and port.
*
* @param host A server host to connect to
* @param port A server port to connect to
* @return {@link ChannelFuture} which will be notified when connection is established.
* @throws InterruptedException if connection process was interrupted
*/
public ChannelFuture connect(String host, int port) throws InterruptedException {
return connect(new InetSocketAddress(host, port));
}
/**
* Connects synchronously to specified remote address.
*
* @param serverAddress A server address to connect to
* @return {@link ChannelFuture} which will be notified when connection is established.
* @throws InterruptedException if connection process was interrupted
*/
public ChannelFuture connect(SocketAddress serverAddress) throws InterruptedException {
setSocketAddress(serverAddress);
return connect().sync();
}
/**
* Connects asynchronously to remote address.
*
* @return Returns the {@link ChannelFuture} which will be notified when this
* channel is active.
*/
public ChannelFuture connectAsync() {
logger.info("Connecting to {}", getSocketAddress());
final Bootstrap b = getBootstrap();
reconnectOnCloseListener.requestReconnect();
final ChannelFuture connectFuture = b.connect();
connectFuture.addListener(connFuture -> {
if (!connectFuture.isSuccess()) {
reconnectOnCloseListener.scheduleReconnect();
return;
}
Channel channel = connectFuture.channel();
logger.info("Client is connected to {}", channel.remoteAddress());
setChannel(channel);
channel.closeFuture().addListener(reconnectOnCloseListener);
});
return connectFuture;
}
@Override
protected Bootstrap createBootstrap() {
final Bootstrap b = new Bootstrap();
b.group(getBossEventLoopGroup())
.channel(NioSocketChannel.class)
.remoteAddress(getSocketAddress())
.handler(new Iso8583ChannelInitializer<>(
getConfiguration(),
getConfigurer(),
getWorkerEventLoopGroup(),
getIsoMessageFactory(),
getMessageHandler()
));
configureBootstrap(b);
b.validate();
reconnectOnCloseListener = new ReconnectOnCloseListener(this,
getConfiguration().getReconnectInterval(),
getBossEventLoopGroup()
);
return b;
}
public ChannelFuture disconnectAsync() {
reconnectOnCloseListener.requestDisconnect();
final Channel channel = getChannel();
if (channel != null) {
final SocketAddress socketAddress = getSocketAddress();
logger.info("Closing connection to {}", socketAddress);
return channel.close();
} else {
return null;
}
}
public void disconnect() throws InterruptedException {
final ChannelFuture disconnectFuture = disconnectAsync();
if (disconnectFuture != null) {
disconnectFuture.await();
}
}
/**
* Sends asynchronously and returns a {@link ChannelFuture}
*
* @param isoMessage A message to send
* @return ChannelFuture which will be notified when message is sent
*/
public ChannelFuture sendAsync(IsoMessage isoMessage) {
Channel channel = getChannel();
if (channel == null) {
throw new IllegalStateException("Channel is not opened");
}
if (!channel.isWritable()) {
throw new IllegalStateException("Channel is not writable");
}
return channel.writeAndFlush(isoMessage);
}
public void send(IsoMessage isoMessage) throws InterruptedException {
sendAsync(isoMessage).sync().await();
}
public boolean isConnected() {
Channel channel = getChannel();
return channel != null && channel.isActive();
}
}